feat(qa): resolve platform-checklist symbol anchors against real source - #15159
Conversation
Step (2) of #13482's ruling, deferred by PR #13786 (step (1), which stripped the rotting `:NNN` suffixes). A citation of `file` plus the symbol it lands in does not rot on an unrelated edit — but nothing RESOLVED it, so a symbol renamed or deleted out of the cited file left the citation reading exactly as it did the day it was verified. - re-author the citations as structured symbol anchors, spelled inline as `path/to/file.ts#symbolName` so the prose that carries a citation's reason survives the change: 609 anchors across the 15 area files; - a resolver in `check-platform-checklist.mjs` that reds when the cited file is absent, when its extension carries no symbols this gate can read, or when the cited symbol is not in the file outside its comments — every segment of a dotted anchor, not just the first; - a shrink-never per-file floor (`scripts/checklist-symbol-anchor-baseline.json`, authored from the new `--anchor-census` mode) so the resolver's population cannot be emptied one deleted `#symbol` at a time — the silent remedy every red here would otherwise have; - 29 self-test rows, two-directional on all three limbs, plus the battery roster floor the file's own handshake comment argued for. Refs #13788 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TouCuN9vQdktkP2MAEgnDC
… the anchor Measured while sweeping the ledger for anchorable citations: four line pins survived PR #13786 in two spellings its detector cannot reach — `AiChatPage .tsx:~605-615` (the tilde INSIDE the colon form; the battery only ever pinned `~:`) and `~L7246-7331`, which carries no colon at all. Both read exactly like the class step (1) removed, and both sat green through it. - the detector gains both branches, with a left AND right boundary on the `L` form so `SQL2019` / `L10n` / `L1` stay silent (six new rows, both directions); - the four citations are repaired — and repairing one surfaced what the resolver is for: `registerRecordShareEndpoints` has not been in `rest-server.ts` for some time (the record-shares routes are registered inline, under no registrar of its own), so the citation had been naming nothing while reading as verified; - README gains the anchor spelling, what is anchorable and why an unresolvable citation stays BARE, the shrink-never floor and its ⛔ MAINTAINER-ONLY authority — and the note that a red here reaches whoever runs the gate, not every PR (#11730); - the battery roster floors are set from measured counts. Refs #13788 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TouCuN9vQdktkP2MAEgnDC
…ledger `check:pm-dispatch-gates` reds on a compound self-test name its COMPOUND_ANCHOR_LEDGER does not classify — the registration the new `selfTestSymbolAnchors` battery owes. Classified `false` beside its five siblings in the same gate: it IS a self-test battery, so its fixtures should be masked away. Refs #13788 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TouCuN9vQdktkP2MAEgnDC
|
Status on the two CI reds relayed for head 1. The failing assertion was precise about what it wanted: No new
So the row is classified Local verdict on 2.
The changeset-family self-tests stay unconditional in Full local re-run on Remote CI on Generated by Claude Code |
|
CI board on head
The Green so far on Still open: Generated by Claude Code |
|
Green on head Generated by Claude Code |
Fixes #13788
Step (2) of #13482's ruling — the half PR #13786 (step (1), the
:NNNstrip) deliberately deferred.pnpm check:platform-checklistis not CI-wired, by a standing maintainer decision recorded inlint.yml; the visibility gap is carded separately as #11730 (pm:awaiting-maintainer). ⇒ A red from this resolver reaches whoever runs the gate, not every PR. No CI wiring is touched here — that decision is the maintainer's and #11730 holds it. Stating this plainly is part of the deliverable: overstating a new check's reach would reproduce the very defect this family is closing — a signal that reads as verified while pointing at nothing.The defect
After step (1) a citation is
fileplus the symbol it lands in, so nothing rots on an unrelated edit. But nothing resolved it: a symbol renamed or deleted out of the cited file left the citation reading exactly as it did the day it was verified — exit-0 by construction, the same shape the line numbers had, one level up. This PR found one of those live:registerRecordShareEndpointshas not been inrest-server.tsfor some time (the record-shares routes are registered inline, under no registrar of their own), and the citation naming it had been reading as verified the whole time.What landed
The citations are re-authored as structured symbol anchors, spelled inline so the prose that carries a citation's reason survives the change:
packages/core/src/security/platform-admin.ts#parsePlatformAdminEmailsThe
#symbolhalf is the machine-readable pin; everything around it is untouched. Rewriting each citation into a bare object per entry would have thrown the reasons away across the whole ledger.A resolver in
scripts/check-platform-checklist.mjs, extending the mechanism the gate already had forenumSource {file, export, expect}. It reds when the cited file is absent, when the extension carries no symbols this gate can read, and when the cited symbol is not in the file outside its comments — every segment of a dotted anchor, not only the first.Deliberately dumb, presence-level, house-ledger style: presence-in-code at identifier boundaries, not a declaration analysis. A symbol surviving only in a docblock is absent (prose about a symbol is not a symbol); a symbol inside a string literal counts as present (much of what this ledger cites is a string — capability names, error codes,
sys_*machine names). Both directions are stated in the gate header and the README rather than left to be discovered.A shrink-never floor —
scripts/checklist-symbol-anchor-baseline.json, one entry per family file, authored from the new read-only--anchor-censusmode. Every red this resolver prints has a second, silent remedy: delete the#symbolhalf and the citation drops back to prose the gate does not read. The floor closes that door, and makes the green say a number instead of meaning nothing once the population reaches zero. Lowering a floor is marked⛔ MAINTAINER-ONLYper thecheck:engine-double-contractfires at CI time, not authoring time — four independent PRs tripped it on brand-new test files in one shift, and a pre-warning in the brief did not prevent it #8435 convention; the roster's own size is pinned beside it, since deleting an entry silences a floor as effectively as zeroing it.Two-direction pins for every rule in the gate's existing multi-entry self-test dispatch (141 → 176 assertions), plus the battery roster floor the file's own handshake comment argued for: the flags answer "did this battery reach its verdict", and the roster answers the other half — a battery that reaches its verdict having run fewer assertions reports a smaller number every leg below reads as a pass.
Two line-pin spellings step (1) could not reach, measured rather than reasoned while sweeping for anchorable citations:
AiChatPage.tsx:~605-615(the tilde inside the colon form — the battery only ever pinned~:) and~L7246-7331, which carries no colon at all. Four survivors, all repaired; the detector gains both branches with a left and right boundary on theLform soSQL2019/L10n/L1stay silent.The census, re-derived (not inherited)
origin/main=ebb08220sourcecitation entries#symbolinsertions, 3 are the deliberate line-pin repairs (proved mechanically — stripping every anchor from each added line reproduces the removed line byte-for-byte)Verification record
Every exit code captured by redirect before any pipe.
The gate's own suite, both halves
check-platform-checklistexits 1 onmaintoday, before this branch exists: four capabilities (batch_endpoints,crud_endpoints,metadata_endpoints,route_generation) are UNCLASSIFIED incoverage.json. Measured on a clean worktree atorigin/mainbefore the first edit, and the error set after this change is byte-identical to it (diffof the two✗lists is empty) — zero new problems, and none of the four is this PR's to fix. Filed as #15167; authoring or waiving four REST capability areas ischecklist-authorcontent work, and folding it in here would have made both changes unreviewable.Derived gate family (
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived after the change set grew — same family both times)A first pass exited 3 (PREREQUISITE NOT MET) on three of these — an uninstalled worktree, NOT MEASURED and never a pass.
pnpm install --frozen-lockfile(EXIT=0), then re-run: the codes above.Always-runs tail
check:pm-dispatch-gatesfirst failed 1 of 1,343 cases — the new battery owed aCOMPOUND_ANCHOR_LEDGERrow inscripts/pm/dispatch-gates.mjs. Registered (false, beside its five siblings in the same gate); 1,343/1,343 pass. There is nocheck:self-test-wiredpnpm alias in this repo — the script is invoked directly, and is green both ways.Ablation — the absent-symbol check
areas/i18n.json→ gate EXIT=1, 5 problems, the fifth beingABSENT SYMBOL — …#zzzRetiredSymbolThatIsGone;absentAnchorSegmentsneutered in memory → 4 problems, 0 non-coverage — proof the red is this check's and nothing else's;Ablation — the roster floor
Two rows deleted from the symbol-anchor battery →
--self-testEXIT=1: battery "selfTestSymbolAnchors…" reported 27 assertions but its floor is 29, and the inline leg refuses too (assertions stopped running, and every leg below would read the smaller count as a pass). Restored → EXIT=0.Control bytes: all 19 changed files scanned, clean; this body scanned too.
Scope fence
docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md) and nothing undercontent/docs/releases/.scripts/pm/dispatch-gates.mjs, one ledger row, because the dispatch-gates self-test demands the registration — declared rather than done silently.scripts/+docs/qa/only), which is what theskip-changesetlabel is for.🤖 Generated with Claude Code
https://claude.ai/code/session_01TouCuN9vQdktkP2MAEgnDC